Skip to content

Add chatIdOrNull to duckchat api module and drop chatID string duplication#8631

Merged
GerardPaligot merged 3 commits into
developfrom
feature/gerard/duckchat-extract-chatid
May 27, 2026
Merged

Add chatIdOrNull to duckchat api module and drop chatID string duplication#8631
GerardPaligot merged 3 commits into
developfrom
feature/gerard/duckchat-extract-chatid

Conversation

@GerardPaligot

@GerardPaligot GerardPaligot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/task/1214962354449266

Description

Promotes the "chatID" query parameter handling out of three impl-private helpers into one function on the public DuckChat interface: chatIdOrNull(uri: Uri): String?. As a side effect, DuckChatContextualViewModel.hasChatId now correctly gates on isDuckChatUrl — previously a crafted URL like https://evil.com?chatID=abc was treated as a Duck.ai chat.

Steps to test this PR

Chat-history clears (DuckChatDataClearingPlugin + DuckAiTabsCleanupPlugin)

  • Open the Duck.ai chat-history screen → long-press to enter select mode → select 2 chats → tap fire → tap Delete all → confirm the chats clear and any open Duck.ai tabs pointing at those chats close.
  • Open the same chat in two browser tabs → from chat-history, delete that chat via the row overflow → confirm both browser tabs close.
  • In chat-history with at least one pinned chat → tap fire (default mode, no selection) → tap Delete all → confirm every chat, pinned included, clears.

Chat resume from input suggestions

  • On the input screen, tap a recent chat suggestion → confirm the chat resumes in Duck.ai with the correct chatID in the URL.
  • If your build surfaces the NTP widget input mode, tap a recent chat suggestion there → same result.

UI changes

n/a


Note

Medium Risk
Changes affect chat deletion/tab matching and contextual fullscreen behavior; the stricter chatID gating fixes mis-detection on non-Duck.ai URLs but alters edge-case URL handling.

Overview
Introduces Uri.toChatIdOrNull(duckChat) in duckchat-api and replaces several impl-local chatID parsers (native input, data clearing, tab cleanup, contextual sheet) with that helper, removing the shared CHAT_ID_PARAM constant.

DuckChatContextualViewModel now treats a URL as having a chat only when it passes isDuckChatUrl, not merely when a chatID query param is present on any host.

buildChatUrl is the single place for resume links: it adds native-input when that setting is on, and the input screen / native input widget call it instead of hand-building URIs. Tests cover buildChatUrl with native input on/off.

Reviewed by Cursor Bugbot for commit 825f604. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Comment thread duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/DuckChat.kt Outdated
@GerardPaligot GerardPaligot force-pushed the feature/gerard/duckchat-extract-chatid branch from 09514e4 to 48d4109 Compare May 21, 2026 16:27
@GerardPaligot GerardPaligot marked this pull request as ready for review May 21, 2026 16:28

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 48d4109. Configure here.

@GerardPaligot GerardPaligot changed the title Add chatIdOrNull to DuckChat api and drop chatID string duplication Add chatIdOrNull to duckchat api module and drop chatID string duplication May 22, 2026
@malmstein malmstein self-assigned this May 22, 2026
The "chatID" query parameter name was duplicated across three call sites
extracting the chatID from a Duck.ai URL, each implementing the lookup
differently and only two of them gating on isDuckChatUrl. Lift the
operation onto the public DuckChat interface as chatIdOrNull(uri) so the
literal lives in one place and the safety guard is enforced for every
consumer — closing a latent foot-gun in DuckChatContextualViewModel.hasChatId
that previously trusted any URL carrying a chatID query param.

DuckChatConstants.CHAT_ID_PARAM is removed; the raw "chatID" string now
only lives in RealDuckChat's private const. URL building stays on the
impl-only DuckChatInternal since every builder is impl-internal.
…lication

The "chatID" query parameter name was extracted in three different shapes
across modules — a custom Uri extension in :app, a private helper in
:duckchat-impl, and an inline lookup in DuckChatContextualViewModel — only
two of which gated on isDuckChatUrl. Lift the operation onto a top-level
Uri.toChatIdOrNull(DuckChat) extension in :duckchat-api so the literal lives
in one place and the safety guard is enforced for every consumer, closing a
latent foot-gun in DuckChatContextualViewModel.hasChatId that previously
trusted any URL carrying a chatID query param.

URL building stays on the impl-only DuckChatInternal since every builder is
impl-internal and never crosses the module boundary.
buildChatUrl was the only Duck.ai URL builder that did not call
nativeInputParameters(), so callers that previously composed their URL
via getDuckChatUrl("", false) + manual appendQueryParameter("chatID", …)
silently lost the native-input=true param after migrating onto
buildChatUrl(chatId). Pull nativeInputParameters() into buildChatUrl so
the "single source of truth for the Duck.ai chat URL shape" actually
produces the same URL shape as every other builder.

Adds two RealDuckChatTest cases that pin the contract: with the native
input setting on, buildChatUrl emits ?chatID=…&native-input=true&duckai=5;
with it off, native-input is absent.
@GerardPaligot GerardPaligot force-pushed the feature/gerard/duckchat-extract-chatid branch from 5a6e8c1 to 825f604 Compare May 26, 2026 07:34

@malmstein malmstein left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not install or smoke-test the APK. nice cleanup — folding the four chatID extractors into one toChatIdOrNull is the right move, and the learnings log from #8666 explicitly flagged the multi-extractor sprawl as a coordination smell, so it's good to see it resolved. centralising chat-url construction through buildChatUrl is also the right direction. just one minor question on the stricter isDuckChatUrl gating in DuckChatContextualViewModel.extractChatId, nothing blocking.

private fun extractChatId(url: String?): String? =
url?.toUri()?.getQueryParameter(CHAT_ID_PARAM)?.takeIf { it.isNotBlank() }
private fun extractChatId(url: String?): String? {
val uri = url?.toUri() ?: return null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this gates on isDuckChatUrl via toChatIdOrNull where it didn't before — the test fake had to be relaxed to recognise duck.ai/duckduckgo.com hosts. probably fine since the sheet always loads duck.ai urls by construction, but worth flagging in case there's an edge case (redirects, malformed urls) where we'd want the old leniency.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's true, but even though there is no check before in this function, the URL is initialized by the DuckChat.getDuckChatUrl function, so we won't break anything. We are just being more restrictive.

@GerardPaligot GerardPaligot merged commit 65a50e2 into develop May 27, 2026
17 checks passed
@GerardPaligot GerardPaligot deleted the feature/gerard/duckchat-extract-chatid branch May 27, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants